package socket;

import java.io.*;
import java.net.Proxy;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;

/**
 * 聊天室服务端
 */
public class Server {

    private ServerSocket serverSocket;
    private static int num = 0 ;
    private ArrayList<PrintWriter> allOut = new ArrayList<>();
    private String name ;


    public Server() {
        try {
            System.out.println("正在启动服务端....");
            log("System-->\"正在启动服务端....\"");
            /*
                创建ServerSocket的同时要申请服务端口,该端口不能与系统其他程序
                开启的端口一致,否则会抛出异常:
                java.io.BindException:address already in uer
                解决办法:
                更换端口
                或杀死占用该端口的程序进程:实际开发中8088很少被占用,
                通常是由于我们启动了两次服务端导致的(第一次启动已经占用了8088,那么第二次启动时
                端口会显示被占用)
             */
            serverSocket = new ServerSocket(8088);
            System.out.println("服务端启动完毕!");
            log("System-->\"服务端启动完毕!\"");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }


    //记录日志文件
    public void log(String s){
        try {
            PrintWriter pw = new PrintWriter( new BufferedWriter(new OutputStreamWriter( new FileOutputStream("./运行日志.txt",true))),true);
            SimpleDateFormat sdf = new SimpleDateFormat();// 格式化时间
            sdf.applyPattern("yyyy-MM-dd HH:mm:ss");// a为am/pm的标记
            Date loginDate = new Date();// 获取当前时间
            String loginTime = sdf.format(loginDate);
            pw.println(loginTime+"\t"+s);
            pw.close();

        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 客户端连接至服务器
     */
    public void start() {
        try {
            System.out.println("等待客户端连接.....");
            log("System-->等待客户端连接.....");
            while (true) {
            /*
                ServerSocket提供的一个重要方法:
                Socket accept()
                该方法是一个阻塞方法,调用时程序会卡住,直到一个客户端与服务端建立连接为止
                此时返回的Socket就可以与连接的客户端进行交互了.
                ※阻塞方法:Socket accept(), Scanner nextLine()
             */
                Socket socket = serverSocket.accept();
                if ("172.88.9.116".equals(socket.getInetAddress().getHostAddress())) {
                    socket.getInputStream().close();
                    socket.getOutputStream().close();
                }

                System.out.println("第"+(allOut.size()+1) + "个客户端连接了!");
                log(">  >   >   >   >System:第" + (allOut.size()+1) + "个客户端已成功连接!" + "IP地址:"+socket.getInetAddress().getHostAddress());

                ClientHandler hanlder = new ClientHandler(socket);
                Thread t = new Thread(hanlder);
                log("线程名称:"+t.getName()+",唯一标识:"+t.getId());
                t.start();



            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    //主程序
    public static void main(String[] args) {

            Server server = new Server();
            server.start();
    }

    /**
     * 将客户端交互的操作再单独的线程上进行
     * 这里的主要工作就是与某个客户端交互
     */
    private class ClientHandler implements Runnable{

        private Socket socket;
        private String host ;//记录客户端的IP地址信息

        //获取客户端的IP地址
        public ClientHandler(Socket socket ) {
            this.socket = socket;
            host = socket.getInetAddress().getHostAddress();
            //host = userName ;
        }


        @Override
        public void run() {
            SimpleDateFormat sdf = new SimpleDateFormat();// 格式化时间
            sdf.applyPattern("yyyy-MM-dd HH:mm:ss");// a为am/pm的标记

            PrintWriter pw = null;
            try {
                //通过socket获取输出流给客户端发送消息
                pw = new PrintWriter(
                        new BufferedWriter(
                                new OutputStreamWriter(
                                        socket.getOutputStream(), StandardCharsets.UTF_8)), true);
                synchronized (allOut) {
                    //将该输出流存入共享合集
                    allOut.add(pw);
                }
                System.out.println(host+"上线了,当前在线人数:"+allOut.size());
                //通过socket获取输入流可以读取来自远端计算机发送过来的消息
                BufferedReader br = new BufferedReader(
                        new InputStreamReader(
                                socket.getInputStream(), StandardCharsets.UTF_8));
                String message;
                /*
                    这里循环读取客户端发送过来的消息可能出现如下异常:
                    java.net.SocketException: Connection reset
                    这个错误是由于远端异常断开导致的(没有进行TCP的挥手断开操作[四次挥手]),
                    该异常无法通过逻辑避免
                 */
                while ((message = br.readLine()) != null ) {

                    if ("".equals(message)) {
                        pw.println("请勿发送空消息!");
                        continue;
                    }
                    System.out.println(host + ":" +message);
                    for (PrintWriter print : allOut) {
                        print.println(host +":"+ message);
                    }
                    log(host+":"+message);
                }

                socket.getInputStream().close();
                br.close();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                //处理客户端断开后的操作
                try {
                    allOut.remove(pw);
                    socket.close();
                    Date logout = new Date();
                    pw.println("**************系统通知**************\n" +
                            "客户端IP:"+host+"\n" +
                            "\n状态:离线"+
                            "\n离线时间:"+sdf.format(logout)+
                            "\n当前在线人数:"+allOut.size()+"人"+
                            "**********************************");

                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}



